package se.slackers.automatunes.ui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.JComponent;

import se.slackers.automatunes.algorithm.AutomataRule;
import se.slackers.automatunes.algorithm.Field;

@SuppressWarnings("serial")
public class AutomataJPanel extends JComponent implements MouseListener {
	private final Dimension fieldSize;
	private final Dimension tileSize;

	private final Field front;
	private final Field back;

	private final AutomataRule rule;

	public AutomataJPanel(final Dimension fieldSize, final Dimension tileSize, final AutomataRule rule) {
		this.fieldSize = fieldSize;
		this.tileSize = tileSize;
		this.rule = rule;

		front = new Field(fieldSize);
		back = new Field(fieldSize);

		clear();
		setPreferredSize(new Dimension(fieldSize.width * tileSize.width, fieldSize.height * tileSize.height));
		addMouseListener(this);
	}

	public Field front() {
		return front;
	}

	public AutomataRule rule() {
		return rule;
	}

	public void clear() {
		synchronized (front) {
			front.clear(0);
		}
	}

	public void step() {
		synchronized (front) {
			rule.apply(front, back);
			front.copy(back);
		}
	}

	@Override
	public void paint(final Graphics g) {
		final int w = getWidth();
		final int h = getHeight();

		g.setColor(Color.black);
		g.fillRect(0, 0, w, h);

		int i = 0;
		for (int y = 0; y < fieldSize.height; y++) {
			for (int x = 0; x < fieldSize.width; x++) {
				final float value = front.get(i++);

				final int c = (int) (value * 0xff);
				g.setColor(new Color(c + (c << 8) + (c << 16)));
				g.fillRect(x * tileSize.width, y * tileSize.height, tileSize.width, tileSize.height);
				g.setColor(Color.gray);
				g.drawRect(x * tileSize.width, y * tileSize.height, tileSize.width, tileSize.height);
			}
		}
	}

	private void toggle(final int x, final int y) {
		synchronized (front) {
			if (front.get(x, y) < 0.5f) {
				front.set(x, y, Field.LIVE);
			} else {
				front.set(x, y, Field.DEAD);
			}
		}
	}

	public void mouseClicked(final MouseEvent e) {
		final int x = e.getX() / tileSize.width;
		final int y = e.getY() / tileSize.height;

		switch (e.getButton()) {
		case MouseEvent.BUTTON1:
			toggle(x, y);
			break;
		case MouseEvent.BUTTON2:
			glider(x, y);
			break;
		case MouseEvent.BUTTON3:
			chaos(x, y);
			break;
		}
		repaint();
	}

	private void glider(final int x, final int y) {
		synchronized (front) {
			front.set(x, y, Field.LIVE);
			front.set(x + 1, y, Field.LIVE);
			front.set(x, y + 1, Field.LIVE);
			front.set(x - 1, y + 1, Field.LIVE);
			front.set(x - 1, y - 1, Field.LIVE);
		}
	}

	private void chaos(final int x, final int y) {
		synchronized (front) {
			for (int i = 0; i < 10; i++) {
				final int xp = (int) (x + (Math.random() - 0.5) * 5);
				final int yp = (int) (y + (Math.random() - 0.5) * 5);
				front.set(xp, yp, Field.LIVE);
			}
		}
	}

	public void mouseEntered(final MouseEvent arg0) {
	}

	public void mouseExited(final MouseEvent arg0) {
	}

	public void mousePressed(final MouseEvent arg0) {
	}

	public void mouseReleased(final MouseEvent arg0) {
	}
}
